/*
 * Copyright (C) 2011-2013 Eric Lasota
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#include "rdx_pragmas.hpp"

#include <stdlib.h>
#include <stdio.h>

#include "rdx.h"
#include "rdx_io.hpp"
#include "rdx_objectmanagement.hpp"
#include "rdx_constants.hpp"
#include "rdx_interpret.hpp"
#include "rdx_marshal.hpp"
#include "rdx_longflow.hpp"
#include "rdx_zonepolicy.hpp"
#include "rdx_plugin.hpp"
#include "rdx_objectloader.hpp"
#include "rdx_appservices.hpp"

using namespace RDX;
using namespace RDX::ObjectManagement;
using namespace RDX::Programmability;
using namespace RDX::Utility;
using namespace RDX::Marshaling;

Security::ZonePolicyPackageHost *zonePolicyHost;

enum
{
	DOMAIN_C2Test = DOMAIN_Custom,
};

int RDX_DECL_API MyPrintLine(OperationContext *ctx, IObjectManager *objm, const Method *m, RuntimeThread *thread,
	RuntimeStackValue *prv)
{
	struct myParameters
	{
		Value<String*> str;
	};

	myParameters *params = ParametersFromPRV<myParameters>(prv);
	wprintf(L"%s\n", params->str.GetValue()->AsChars());

	return RuntimeState::Active;
}

int RDX_DECL_API MyPrintLineInt(OperationContext *ctx, IObjectManager *objm, const Method *m, RuntimeThread *thread,
	RuntimeStackValue *prv)
{
	struct myParameters
	{
		Value<Int> i;
	};

	myParameters *params = ParametersFromPRV<myParameters>(prv);
	wprintf(L"%i\n", params->i);

	return RuntimeState::Active;
}


int RDX_DECL_API MyStackTrace(OperationContext *ctx, IObjectManager *objm, const Method *m, RuntimeThread *thread,
	RuntimeStackValue *prv)
{
	RuntimeTrace trace;
	bool traced = thread->Trace(trace);
	while(traced)
	{
		const String *filename;
		LargeInt line;
		RDX_RuntimeTrace_GetFileNameAndLineNumber(&trace, &filename, &line);
		wprintf(L"Method: %s Line %i File %s\n", GCInfo::From(trace.method)->gstSymbol->AsChars(), line, filename->AsChars());
		traced = (RDX_RuntimeTrace_TraceParent(&trace, &trace) != 0);
	}
	return RuntimeState::Active;
}

int RDX_DECL_API MyTestDirectInvokeGC(OperationContext *ctx, IObjectManager *objm, const Method *m, RuntimeThread *thread,
	RuntimeStackValue *prv)
{
	struct myParameters
	{
		Value<const String *> str;
	};

	myParameters *params = ParametersFromPRV<myParameters>(prv);
	objm->CollectGarbage(ctx);
	wprintf(L"Testing parameter GC\n");
	wprintf(L"%s\n", params->str.GetValue()->AsChars());

	return RuntimeState::Active;
}

int RDX_DECL_API MyReentryTest(OperationContext *ctx, IObjectManager *objm, const Method *m, RuntimeThread *thread,
	RuntimeStackValue *prv)
{
	struct myParameters
	{
		Value<Method *> callback;
		Value<const String *> str;
	};

	myParameters *params = ParametersFromPRV<myParameters>(prv);

	ReturnAndParameterFrame<Value<Int>, CallFrame2<const String *, const String *> > *reentryFrame;

	RDX_TRY(ctx)
	{
		RDX_PROTECT(ctx, thread->Precall(ctx, RDX_MARSHAL_FRAME(reentryFrame)));
		RDX_PROTECT_ASSIGN(ctx, reentryFrame->parameters.value1.GetValue(), objm->CreateStringSub(ctx, params->str.GetValue(), 0, 4));
		RDX_PROTECT_ASSIGN(ctx, reentryFrame->parameters.value2.GetValue(), objm->CreateStringSub(ctx, params->str.GetValue(), 4, 4));

		int returnedStatus;
		RDX_PROTECT_ASSIGN(ctx, returnedStatus, params->callback.GetValue()->Invoke(ctx, thread, 1000) );
		if(returnedStatus == RuntimeState::Exception)
		{
			bool recovered = thread->Recover(ctx);
			if(!recovered)
				return RuntimeState::Exception;
		}
	}
	RDX_CATCH(ctx)
	{
		return RuntimeState::Exited;
	}
	RDX_ENDTRY


	return RuntimeState::Active;
}

int RDX_DECL_API MyWriteObject(OperationContext *ctx, IObjectManager *objm, const Method *m, RuntimeThread *thread,
	RuntimeStackValue *prv)
{
	
	struct myParameters
	{
		Value<Bool> committed;
		Value<void *> obj;
		Value<const String *> filename;
	};
	
	RDX_TRY(ctx)
	{
		myParameters *params = ParametersFromPRV<myParameters>(prv);
		params->committed.GetValue() = TrueValue;
		RDX::IO::IFileStream *fs = AppServices::GetFileSystem()->Open(params->filename.GetValue()->AsChars(), true);
		if(!fs)
			return RuntimeState::Active;
		RDX_PROTECT(ctx, objm->SaveObject(ctx, params->obj.GetValue(), fs, true));
		fs->Close();
	}
	RDX_CATCH(ctx)
	{
	}
	RDX_ENDTRY

	return RuntimeState::Active;
}

int RDX_DECL_API MyReadObject(OperationContext *ctx, IObjectManager *objm, const Method *m, RuntimeThread *thread,
	RuntimeStackValue *prv)
{
	
	struct myParameters
	{
		Value<const String *> filename;
	};
	struct myReturnValues
	{
		Value<void *> obj;
	};
	
	RDX_TRY(ctx)
	{
		myParameters *params = ParametersFromPRV<myParameters>(prv);
		myReturnValues *rv = ReturnValuesFromPRV<myReturnValues>(prv);
		rv->obj.GetValue() = NULL;
		CRef<void> obj;

		Utility::RuntimeObjectLoaderHost loaderHost(zonePolicyHost, DOMAIN_Runtime, AppServices::GetFileSystem());
		loaderHost.SetPackage(params->filename.GetValue()->AsChars(), true);
		RDX_PROTECT_ASSIGN(ctx, rv->obj.GetValue(), objm->LoadObject(ctx, &loaderHost));
	}
	RDX_CATCH(ctx)
	{
	}
	RDX_ENDTRY

	return RuntimeState::Active;
}

struct CMyTypeHost : public INativeTypeHost
{
	void SetTypeProperties(IObjectManager *objm, StructuredType *st) const
	{
		const String *str = GCInfo::From(st)->gstSymbol;
		if(!str)
			return;
	}

	NativeCallback HookMethod(const String *str) const
	{
		if(str->Equal("Apps.Common.Console/methods/WriteLine(Core.string)"))
			return MyPrintLine;
		else if(str->Equal("Apps.Common.Console/methods/WriteLine(Core.int)"))
			return MyPrintLineInt;
		else if(str->Equal("Apps.Common.Tests/methods/TestReentry(Core.string,#DS-#TT-(Core.int)#PL-(Core.string,Core.string))"))
			return MyReentryTest;
		else if(str->Equal("Apps.Common.Tests/methods/StackTrace()"))
			return MyStackTrace;
		else if(str->Equal("Apps.RegressionTest.RegressionTest/methods/TestDirectInvokeGC(Core.string)"))
			return MyTestDirectInvokeGC;
		else if(str->Equal("Apps.Common.Serialization/methods/WriteObject(notnull Core.string,notnull Core.Object,Core.bool)"))
			return MyWriteObject;
		else if(str->Equal("Apps.Common.Serialization/methods/ReadObject(notnull Core.string)"))
			return MyReadObject;

		return NULL;
	}
};

CMyTypeHost myTypeHost;

struct AppPlugin
{
	void *lib;
	const Programmability::PrecompiledCodeModule *pccm;
	const INativeTypeHost *plugin;
	AppPlugin *nextPlugin;
};

AppPlugin *lastPlugin;
AppPlugin *firstPlugin;

static unsigned int numPlugins = 0;
static unsigned int numPCCM = 0;
static const INativeTypeHost **pluginsList;
static const PrecompiledCodeModule **pccmList;

int main(int argc, const char **argv)
{
	Allocator alloc;
	alloc.reallocFunc = AppServices::Realloc;
	bool exportBin = false;
	const char *xilPath = NULL;

	int returnValue = 0;

	// Scan for execution arguments
	while(true)
	{
		if(argc == 1)
			break;
		const char *nextArg = argv[1];
		if(!strcmp(nextArg, "-plugin"))
		{
			if(argc == 2)
			{
				fprintf(stderr, "No argument specified for -plugin");
				return -1;
			}
			void *lib = AppServices::LoadLib(argv[2]);
			if(lib)
			{
				AppPlugin *plugin = new AppPlugin();
				plugin->lib = lib;
				plugin->nextPlugin = NULL;
				const void *(*retrFunc)() = static_cast<const void *(*)()>(AppServices::GetProc(lib, "RDXPlugin"));
				if(retrFunc)
				{
					plugin->plugin = static_cast<const INativeTypeHost *>(retrFunc());
					numPlugins++;
				}
				else
					plugin->plugin = NULL;
				retrFunc = static_cast<const void *(*)()>(AppServices::GetProc(lib, "RDXPCCM"));
				if(retrFunc)
				{
					plugin->pccm = static_cast<const PrecompiledCodeModule *>(retrFunc());
					numPCCM++;
				}
				else
					plugin->pccm = NULL;
				if(firstPlugin)
					lastPlugin->nextPlugin = plugin;
				else
					firstPlugin = plugin;
				lastPlugin = plugin;
			}
			else
			{
				fprintf(stderr, "Couldn't open plugin %s\n", argv[2]);
				return -1;
			}
			argc -= 2;
			argv += 2;
		}
		else
			break;
	}

	// Flatten
	{
		pluginsList = new const INativeTypeHost*[numPlugins+3];
		pccmList = new const PrecompiledCodeModule*[numPCCM];
		int pccmIdx = 0;
		int pluginIdx = 0;
		pluginsList[pluginIdx++] = RDX_GetNumericTypesPlugin();
		for(AppPlugin *plug=firstPlugin;plug;plug=plug->nextPlugin)
		{
			if(plug->plugin)
				pluginsList[pluginIdx++] = plug->plugin;
			if(plug->pccm)
				pccmList[pccmIdx++] = plug->pccm;
		}

		pluginsList[pluginIdx++] = &myTypeHost;
		pluginsList[pluginIdx++] = NULL;
	}
	
	CombinedNativeTypeHost pluginsTypeHost(pluginsList);

	RDX_Initialize();
	ICodeProvider *myCodeProvider = RDX_CreateInterpreterCodeProvider(&alloc, pccmList, static_cast<LargeInt>(numPCCM));

	IObjectManager *om = RDX_CreateObjectManager(&alloc, &pluginsTypeHost, myCodeProvider);


	{
		CRef<const String> str = NULL;
		CRef<void> obj = NULL;
		//MyPackageHost host;
		CRef<Method> testMethod;

		OperationContext myContext(om);

		{
			RDX_TRY(&myContext)
			{
				zonePolicyHost = new Security::ZonePolicyPackageHost(&myContext, om, AppServices::GetFileSystem(), true);

				{
					RuntimeObjectLoaderHost zoneLoaderHost(zonePolicyHost, DOMAIN_Runtime, AppServices::GetFileSystem());
					zoneLoaderHost.SetPackage(RDX_STATIC_STRING("Apps.zonepolicy"), true);

					CRef<Security::Zone*> zones;
					
					RDX_PROTECT_ASSIGN(&myContext, zones, om->LoadObject(&myContext, &zoneLoaderHost).Cast<Security::Zone*>());
					RDX_PROTECT(&myContext, zonePolicyHost->LoadZones(&myContext, zones));
				}


				{
					CRef<const String> className;
					CRef<const String> mainExtension;
					RDX_PROTECT_ASSIGN(&myContext, className, om->CreateStringASCII(&myContext, argv[1]) );
					RDX_PROTECT_ASSIGN(&myContext, mainExtension, om->CreateStringASCII(&myContext, "/methods/main(#Core.string[C])") );
					RDX_PROTECT_ASSIGN(&myContext, str, om->CreateStringConcatenated(&myContext, className, mainExtension) );
				}
				RDX_PROTECT_ASSIGN(&myContext, testMethod, om->LookupSymbol(&myContext, str, zonePolicyHost).Cast<Method>() );

				/*
				{
					CRef<const String> str = om->CreateStringASCII(&myContext, "Apps.RegressionTest.RegressionTest/methods/TestDirectInvokeGC(Core.string)", -1, true);
					if(str)
					{
						CRef<Method> gcmeth;
						RDX_PROTECT_ASSIGN(&myContext, gcmeth, om->LookupSymbolSimple(&myContext, str).Cast<Method>() );

						CRef<RuntimeThread> myThread;
						RDX_PROTECT_ASSIGN(&myContext, myThread, om->CreateThread(&myContext, 102400) );
						
						Value<const String *> *mParams;
						RDX_PROTECT(&myContext, myThread->Precall(&myContext, RDX_MARSHAL_NORETURN(mParams)));
						mParams->value = om->CreateStringASCII(&myContext, "Test direct GC");

						int returnedStatus;
						RDX_PROTECT_ASSIGN(&myContext, returnedStatus, gcmeth->Invoke(&myContext, myThread, 1000) );
					}
				}
				*/

				if(!testMethod)
				{
					fprintf(stderr, "Failed to find main method in provided class\n");
					return -1;
				}

				CRef<ArrayOfType> stringArrayType;
				RDX_PROTECT_ASSIGN(&myContext, stringArrayType, om->CreateArrayType(&myContext, om->GetBuiltIns()->st_String, 1, true) );

				CRef<const String*> myArgs;
				RDX_PROTECT_ASSIGN(&myContext, myArgs, om->Create1DArray<const String*>(&myContext, argc - 2, stringArrayType) );

				for(LargeInt i=2;i<argc;i++)
				{
					RDX_PROTECT_ASSIGN(&myContext, myArgs[i-2], om->CreateStringASCII(&myContext, argv[i]) );
				}

				ReturnAndParameterFrame<Value<Int>, CallFrame1<const String **> > *mParams;
				
				CRef<RuntimeThread> myThread;
				RDX_PROTECT_ASSIGN(&myContext, myThread, om->CreateThread(&myContext, 102400) );

				RDX_PROTECT(&myContext, myThread->Precall(&myContext, RDX_MARSHAL_FRAME(mParams)));
				mParams->parameters.value1.GetValue() = myArgs;

				int returnedStatus;
				RDX_PROTECT_ASSIGN(&myContext, returnedStatus, testMethod->Invoke(&myContext, myThread, 10000000) );

				while(returnedStatus == RuntimeState::Exception)
				{
					bool recovered = true;
					RDX_PROTECT_ASSIGN(&myContext, recovered, myThread->Recover(&myContext));
					if(!recovered)
						break;
					RDX_PROTECT_ASSIGN(&myContext, returnedStatus, myThread->Resume(&myContext, 1000000) );
				}

				if(returnedStatus == RuntimeState::Exception)
				{
					wprintf(L"Exception.  Type: %s\n", GCInfo::From(GCInfo::From(myThread->ex)->containerType)->gstSymbol->AsChars());
					RuntimeTrace trace;
					bool traced = myThread->Trace(trace);
					while(traced)
					{
						const String *filename;
						LargeInt line;
						RDX_RuntimeTrace_GetFileNameAndLineNumber(&trace, &filename, &line);
						wprintf(L"Method: %s Line %i File %s\n", GCInfo::From(trace.method)->gstSymbol->AsChars(), line, filename->AsChars());
						traced = (RDX_RuntimeTrace_TraceParent(&trace, &trace) != 0);
					}
				}

				returnValue = mParams->returnValues.GetValue();
			}
			RDX_CATCH(&myContext)
			{
				wprintf(L"An internal error occurred: %i\n", errorCode);
			}
			RDX_ENDTRY
		}
	}
	om->Shutdown();
	myCodeProvider->Shutdown();

	if(zonePolicyHost)
		delete zonePolicyHost;

	return returnValue;
}
